#include <linux/usb.h>

#define VENDOR_ID	0x0606
#define PRODUCT_ID	0x0606

static struct usb_device_id id_table[] = {
	{USB_DEVICE(VENDOR_ID, PRODUCT_ID)}, {},
};

MODULE_DEVICE_TABLE(usb, id_table);

static struct usb_device* m_udev;
static const char *types[] = {"control","isoc","bulk","int"};
static int tohostpipe, todevicepipe;
static int tohost_epaddr, todevice_epaddr;
static const int buffer_length=16;
static void *buffer;
static struct urb *urb;

static void detect_altsetting(struct usb_device *udev, struct usb_host_interface *as, int index){
	int l;
	struct usb_host_endpoint *ep;
	
	if(as == 0) return;
	printk("<3>%d endpoints in altsetting %d.\n", as->desc.bNumEndpoints, index);
	for(l=0;l<as->desc.bNumEndpoints;l++){
		ep = &as->endpoint[l];
		if(ep == 0) continue;
		printk("<3>endpoint %d: direction '%s' type '%s'\n",
			l,
			(ep->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK)==USB_DIR_IN?"to host":"to device",
			types[ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK]);

		if ( ((ep->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK)==USB_DIR_IN) && 
			((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)==USB_ENDPOINT_XFER_BULK) ) {
			tohost_epaddr=ep->desc.bEndpointAddress;
			tohostpipe=usb_rcvbulkpipe(udev, tohost_epaddr);
		}
		
		if ( ((ep->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK)==USB_DIR_OUT) && 
			((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)==USB_ENDPOINT_XFER_BULK) ) {
			todevice_epaddr=ep->desc.bEndpointAddress;
			todevicepipe=usb_sndbulkpipe(udev, todevice_epaddr);
		}
	}
	
	printk("<3> tohostpipe: %d todevicepipe: %d\n", tohost_epaddr, todevice_epaddr);
}

static void detect_interface(struct usb_device *udev, struct usb_interface *ifp, int index){
	int k;
	
	if(ifp == 0) return;
	printk("<3>%d altsettings in interface %d.\n", ifp->num_altsetting, index);
	for(k=0;k<ifp->num_altsetting;k++)
		detect_altsetting(udev, &ifp->altsetting[k], k);
}

static void detect_configuration(struct usb_device *udev, struct usb_config_descriptor *cfg, int index){
	int j;

	if(cfg == 0) return;
	printk("<3>%d interfaces in configuration %d.\n", cfg->bNumInterfaces, index);
	for(j=0;j<cfg->bNumInterfaces;j++){
		detect_interface(udev, udev->config[index].interface[j], j);
	}
}

void m_complete(struct urb *urb){
	char *buf = (char *)buffer;
	
	if(urb->status){
		printk("<3>Error in urb.\n");
		return;
	}
	printk("<3>urb finished.\n");
	printk("<3>data: %d %d\n", (int)buf[0], (int)buf[1]);
}

int m_probe(struct usb_interface *interface, const struct usb_device_id *id){
	struct usb_device *udev=interface_to_usbdev(interface);
	int i;
	m_udev=usb_get_dev(udev);

	printk("<3>Device (Vendor %x Product %x) has %d configurations\n", 
		VENDOR_ID, 
		PRODUCT_ID,
		udev->descriptor.bNumConfigurations);

	for(i=0;i<udev->descriptor.bNumConfigurations;i++)
		detect_configuration(udev, &udev->config[i].desc, i);

	if(tohostpipe==0 || todevicepipe==0) return 0;
	
	{
		char data[8]="12345678";
		int actual_length;
		int result;
		if( (result=usb_bulk_msg(udev, todevicepipe, data, 8, &actual_length, 0 /*wait forever*/)) )
		  {
			printk("<3>Fail to call 'usb_bulk_msg'. result = %d. \n", result);
			return -1;
		  }
		printk("<3>bulk sent, actual_length: %d.\n", actual_length);
	}
	
	{
		urb = usb_alloc_urb(0, GFP_KERNEL);
		if(!urb){
			printk("<3>Cannot allocate urb.\n");
			return -1;
		}
		buffer = usb_buffer_alloc(udev, buffer_length, GFP_KERNEL, &urb->transfer_dma);
		if(!buffer){
			printk("<3>Cannot allocate buffer.\n");
			return -1;
		}
		usb_fill_bulk_urb(urb, udev, tohostpipe, buffer, buffer_length, m_complete, NULL);
		if (usb_submit_urb(urb, GFP_ATOMIC)){
			printk("<3>Cannot submit urb.\n");
			return -1;
		}
	}
	
	printk("<3>bulk sent, urb submitted.\n");
	
	return 0;
}

void m_disconnect(struct usb_interface *interface){
	usb_put_dev(m_udev);
}

struct usb_driver m_driver={
	.name = "host",
	.probe = m_probe,
	.disconnect = m_disconnect,
	.id_table = id_table,
};

int __init m_init(void)
{
	if(usb_register(&m_driver))
		return -1;
	printk("<3>module 'host' installed.\n");
	return 0;
}

void __exit m_exit(void)
{
	usb_deregister(&m_driver);
	if(urb){
		if(buffer)
			usb_buffer_free(m_udev, buffer_length, buffer, urb->transfer_dma);
		usb_free_urb(urb);
	}
}

module_init(m_init);
module_exit(m_exit);
MODULE_LICENSE("GPL");
